package com.kyle.redpacket.service;

import com.kyle.redpacket.dao.entity.RedPacket;
import com.kyle.redpacket.dao.entity.RedRecord;
import com.kyle.redpacket.util.RedPacketUtil;
import com.kyle.redpacket.util.SnowFlake;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 抢红包服务
 */
@Service
@Slf4j
public class RedPacketServiceImpl implements IRedPacketService {

    //存储至缓存系统Redis时定义的Key前缀
    private static final String KEY_PREFIX = "redis:red:packet:";

    private final SnowFlake snowFlake = new SnowFlake(2, 3);
    //定义Redis操作模板组件
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    //自动注入红包业务逻辑处理过程数据记录接口服务
    @Autowired
    private IRedService redService;


    /**
     * 发红包
     *
     * @return
     * @throws Exception
     */
    @Override
    public RedPacket handOut(RedPacket dto) throws Exception {
        if (dto.getTotal() > 0 && dto.getAmount() > 0) {
            //生成随机金额
            List<Long> list = RedPacketUtil.divideRedPackage(dto.getAmount(), dto.getTotal());

            //生成红包全局唯一标识,并将随机金额、个数入缓存
            String timestamp = String.valueOf(System.nanoTime());

            //根据缓存key的前缀与其他信息拼接成一个新的用于存储随机金额列表的Key
            final String redId = KEY_PREFIX + dto.getUserId() + ":" + timestamp;

            List<RedRecord> redRecords = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                RedRecord redRecord = new RedRecord();
                redRecord.setRedPacketId(timestamp);
                redRecord.setAmount(list.get(i));
                redRecord.setOrderNo((long) i);
                redRecords.add(redRecord);
            }

            redRecords.forEach(redRecord -> redisTemplate.opsForList().leftPush(redId, redRecord));

            String redTotalKey = redId + ":total";
            //将随机金额列表存入缓存列表中
            redisTemplate.opsForValue().set(redTotalKey, dto.getTotal());

            //异步记录红包发出的记录-包括个数与随机金额
            dto.setRedId(timestamp);
            redService.recordRedPacket(dto, redRecords);
            //将红包的全局唯一标识串返回给前端
            return dto;
        }
        else {
            throw new Exception("系统异常-分发红包-参数不合法!");
        }
    }

    /**
     * 加分布式锁的情况
     * 抢红包-分“点”与“抢”处理逻辑
     *
     * @return
     * @throws Exception
     */
    @Override
    public RedRecord rob(Long userId, Long robUserId, String redId) throws Exception {
        ValueOperations<Object, Object> valueOperations = redisTemplate.opsForValue();

        //根据缓存key的前缀与其他信息拼接成一个新的用于存储随机金额列表的Key
        final String redIdx = KEY_PREFIX + userId + ":" + redId;

        final String redIdRob = redIdx + ":" + robUserId + "-rob";
        //用户是否抢过该红包
        Object obj = valueOperations.get(redIdRob);
        if (obj != null) {
            return (RedRecord) obj;
        }

        //"点红包"
        Boolean res = click(redIdx);
        if (res) {
            //上锁：一个红包每个人只能抢一次随机金额；一个人每次只能抢到红包的一次随机金额  即要永远保证 1对1 的关系
            final String lockKey = redIdx + ":" + robUserId + "-lock";
            Boolean lock = valueOperations.setIfAbsent(lockKey, robUserId, 24L, TimeUnit.HOURS);
            try {
                if (Boolean.TRUE.equals(lock)) {
                    //"抢红包"-且红包有钱
                    Object value = redisTemplate.opsForList().rightPop(redIdx);
                    if (value != null) {
                        //红包个数减一
                        String redTotalKey = redIdx + ":total";

                        long currTotal = valueOperations.get(redTotalKey) != null ?
                                         (Long) valueOperations.get(
                                                 redTotalKey) : 0;
                        valueOperations.set(redTotalKey, currTotal - 1);


                        //将红包金额返回给用户的同时，将抢红包记录入数据库与缓存
                        RedRecord result = (RedRecord) value;
                        //记录信息到数据库中
                        redService.recordRobRedPacket(robUserId, result);

                        valueOperations.set(redIdRob, result, 24L, TimeUnit.HOURS);

                        log.info("当前用户抢到：userId={} key={} 金额={} ", userId, redId, result);
                        return result;
                    }

                }
            } catch (Exception e) {
                throw new Exception("系统异常-抢红包-加分布式锁失败!");
            }
        }
        return null;
    }

    /**
     * 不加分布式锁的情况
     * 抢红包-分“点”与“抢”处理逻辑
     * @param userId
     * @param redId
     * @return
     * @throws Exception
     */
//    @Override
//    public BigDecimal红包了 rob(Integer userId,String redId) throws Exception {
//        ValueOperations valueOperations=redisTemplate.opsForValue();
//
//        //用户是否抢过该红包
//        Object obj=valueOperations.get(redId+userId+":rob");
//        if (obj!=null){
//            return new BigDecimal(obj.toString());
//        }
//
//        //"点红包"
//        Boolean res=click(redId);
//        if (res){
//            //"抢红包"-且红包有钱
//            Object value=redisTemplate.opsForList().rightPop(redId);
//            if (value!=null){
//                //红包个数减一
//                String redTotalKey = redId+":total";
//
//                Integer currTotal=valueOperations.get(redTotalKey)!=null? (Integer) valueOperations.get(redTotalKey) : 0;
//                valueOperations.set(redTotalKey,currTotal-1);
//
//
//                //将红包金额返回给用户的同时，将抢红包记录入数据库与缓存
//                BigDecimal result = new BigDecimal(value.toString()).divide(new BigDecimal(100));
//                redService.recordRobRedPacket(userId,redId,new BigDecimal(value.toString()));
//
//                valueOperations.set(redId+userId+":rob",result,24L,TimeUnit.HOURS);
//
//                log.info("当前用户抢到红包了：userId={} key={} 金额={} ",userId,redId,result);
//                return result;
//            }
//
//        }
//        return null;
//    }


    /**
     * 点红包-返回true，则代表红包还有，个数>0
     *
     * @throws Exception
     */
    private Boolean click(String redId) throws Exception {
        ValueOperations valueOperations = redisTemplate.opsForValue();

        String redTotalKey = redId + ":total";
        Object total = valueOperations.get(redTotalKey);
        return total != null && Integer.valueOf(total.toString()) > 0;
    }
}




























